---
id: json-serialization
title: 23. JSON 序列化
sidebar_label: 23. JSON 序列化
---

:::important 版本说明

以下内容仅限 `Furion 1.16.0 +` 版本使用。

:::

## 23.1 什么是 `JSON`

> JSON (JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c 制定的 js 规范)的一个子集，采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写，同时也易于机器解析和生成，并有效地提升网络传输效率。

简单来说，JSON，是一种数据格式，在与后端的数据交互中有较为广泛的应用。

## 23.2 关于序列化库

目前在 C# 语言中有两个主流的 `JSON` 序列化操作库：

- `System.Text.Json`：`.NET Core` 内置 `JSON` 序列化库，也是 `Furion` 框架默认实现
- `Newtonsoft.Json`：目前使用人数最多的 `JSON` 序列化库，需要安装 `Microsoft.AspNetCore.Mvc.NewtonsoftJson` 拓展包

由于目前 `System.Text.Json` 相比 `Newtonsoft.Json` 功能和稳定性有许多不足之处，比如循环引用问题在 `System.Text.Json` 无解。但在 `.NET 6` 之后得到解决。

`Furion` 框架为了解决多种序列化工具配置和用法上的差异问题，抽象出了 `IJsonSerializerProvider` 接口。

## 23.3 `IJsonSerializerProvider` 接口

`Furion` 框架提供了 `IJsonSerializerProvider` 接口规范，同时**要求实现该接口的实体都必须采用单例模式**，该接口定义代码如下：

```cs
namespace Furion.JsonSerialization
{
    /// <summary>
    /// Json 序列化提供器
    /// </summary>
    public interface IJsonSerializerProvider
    {
        /// <summary>
        /// 序列化对象
        /// </summary>
        /// <param name="value"></param>
        /// <param name="jsonSerializerOptions"></param>
        /// <param name="inherit">是否继承全局配置，默认 true</param>
        /// <returns></returns>
        string Serialize(object value, object jsonSerializerOptions = default, bool inherit = true);

        /// <summary>
        /// 反序列化字符串
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <param name="jsonSerializerOptions"></param>
        /// <param name="inherit">是否继承全局配置，默认 true</param>
        /// <returns></returns>
        T Deserialize<T>(string json, object jsonSerializerOptions = default, bool inherit = true);

        /// <summary>
        /// 返回读取全局配置的 JSON 选项
        /// </summary>
        /// <returns></returns>
        object GetSerializerOptions();
    }
}
```

:::important 默认实现

`SystemTextJsonSerializerProvider` 类是 `IJsonSerializerProvider` 接口的默认实现，在应用启动时已默认注册。

:::

## 23.4 如何使用

### 23.4.1 获取序列化对象

`Furion` 框架提供了两种方式获取 `IJsonSerializerProvider` 实例：

- 构造函数注入 `IJsonSerializerProvider`
- 静态类 `JSON.GetJsonSerializer()` 方式，**查看 [JSON 静态类](./global/json.mdx)**

如：

```cs {10,13}
using Furion.DynamicApiController;
using Furion.JsonSerialization;

namespace Furion.Application
{
    public class JsonDemo : IDynamicApiController
    {
        private readonly IJsonSerializerProvider _jsonSerializer;
        private readonly IJsonSerializerProvider _jsonSerializer2;
        public JsonDemo(IJsonSerializerProvider jsonSerializer)
        {
            _jsonSerializer = jsonSerializer;
            _jsonSerializer2 = JSON.GetJsonSerializer();
        }
    }
}
```

### 23.4.2 序列化对象

```cs
public string GetText()
{
    return _jsonSerializer.Serialize(new
    {
        Id = 1,
        Name = "Furion"
    });
}
```

### 23.4.3 反序列化字符串

```cs
public object GetObject()
{
    var json = "{\"Id\":1,\"Name\":\"Furion\"}";
    var obj = _jsonSerializer.Deserialize<object>(json);
    return obj;
}
```

:::important 特别注意

`System.Text.Json` 默认反序列化大小写敏感，也就是不完全匹配的属性名称不会自动赋值。这时候我们可以全局配置或单独配置。

- 全局配置

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options => {
            options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
        });
```

- 单独配置

```cs
var obj = _jsonSerializer.Deserialize<object>(json, new JsonSerializerOptions
   {
       PropertyNameCaseInsensitive = true
   });
```

:::

### 23.4.4 序列化更多配置

`Furion` 框架不推荐一个框架中有多种序列化实现类，也就是说使用 `System.Text.Json` 就不要使用 `Newtonsoft.Json`，反之亦然。

如需配置更多选项，只需创建 `JsonSerializerOptions` 配置对象即可，如：

```cs {5}
var json =  _jsonSerializer.Serialize(new
            {
                Id = 1,
                Name = "Furion"
            }, new JsonSerializerOptions {
                WriteIndented = true
            });
```

## 23.5 高级用法

### 23.5.1 自定义序列化提供器

正如上文所说，`Furion` 默认的 `IJsonSerializerProvider` 实现方式是 `System.Text.Json` 库，如需替换为 `Newtonsoft.Json`，只需以下步骤即可：

1. 安装 `Microsoft.AspNetCore.Mvc.NewtonsoftJson` 拓展，并在 `Startup.cs` 中注册

```cs {2}
services.AddControllersWithViews()
        .AddNewtonsoftJson();
```

2. 实现 `IJsonSerializerProvider` 提供器

```cs {10}
using Furion.DependencyInjection;
using Furion.JsonSerialization;
using Newtonsoft.Json;

namespace Furion.Core
{
    /// <summary>
    /// Newtonsoft.Json 实现
    /// </summary>
    public class NewtonsoftJsonSerializerProvider : IJsonSerializerProvider, ISingleton
    {
        /// <summary>
        /// 序列化对象
        /// </summary>
        /// <param name="value"></param>
        /// <param name="jsonSerializerOptions"></param>
        /// <param name="inherit">是否继承全局配置，默认 true</param>
        /// <returns></returns>
        public string Serialize(object value, object jsonSerializerOptions = null, bool inherit = true)
        {
            return JsonConvert.SerializeObject(value, (jsonSerializerOptions ?? (inherit ? GetSerializerOptions() : default)) as JsonSerializerSettings);
        }

        /// <summary>
        /// 反序列化字符串
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="json"></param>
        /// <param name="jsonSerializerOptions"></param>
        /// <param name="inherit">是否继承全局配置，默认 true</param>
        /// <returns></returns>
        public T Deserialize<T>(string json, object jsonSerializerOptions = null, bool inherit = true)
        {
            return JsonConvert.DeserializeObject<T>(json, (jsonSerializerOptions ?? (inherit ? GetSerializerOptions() : default)) as JsonSerializerSettings);
        }

        /// <summary>
        /// 返回读取全局配置的 JSON 选项
        /// </summary>
        /// <returns></returns>
        public object GetSerializerOptions()
        {
            return App.GetOptions<MvcNewtonsoftJsonOptions>()?.SerializerSettings;
        }
    }
}
```

### 23.5.2 序列化属性名大写（属性原样输出）

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options => {
            options.JsonSerializerOptions.PropertyNamingPolicy = null;
            // options.JsonSerializerOptions.DictionaryKeyPolicy = null;    // 配置 Dictionary 类型序列化输出
        });
```

- `Newtonsoft.Json` 方式

```cs
services.AddControllersWithViews()
        .AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ContractResolver = new DefaultContractResolver();
        });
```

:::important 特别注意

采用 `Newtonsoft.Json` 方式接口返回值能够正常输出，但是 `Swagger` 界面中的 `Example Values` 依然显示小写字母开头的属性，这时只需要再添加 `System.Text.Json` 配置即可，如：

```cs
.AddJsonOptions(options => {
            options.JsonSerializerOptions.PropertyNamingPolicy = null;
        });
```

主要原因是 `Swagger` 拓展包底层依赖了 `System.Text.Json`。

:::

### 23.5.3 时间格式化

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Converters.AddDateFormatString("yyyy-MM-dd HH:mm:ss");
        });
```

:::note 小提示

如果使用使用了 `DateTimeOffset` 类型，那么可以设置 `.AddDateFormatString("yyyy-MM-dd HH:mm:ss", true)` 第二个参数为 `true`，自动转换成本地时间。

如果使用了 `Mysql` 数据库，且使用了 `Pomelo.EntityFrameworkCore.MySql` 包，那么会出现时区问题，比如少 8 小时，可以尝试配置第二个参数为 `true`。

:::

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

```cs
services.AddControllersWithViews()
        .AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
        });
```

### 23.5.4 忽略循环引用

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
        });
```

:::important 特别说明

在 `.NET 5` 中，`System.Text.Json` 并不支持处理循环引用问题，以上的解决方案仅限用于 `.NET 6 Preview 2+`。😂

:::

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

```cs
services.AddControllersWithViews()
        .AddNewtonsoftJson(options =>
        {
            options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
        });
```

### 23.5.5 包含成员字段序列化

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.IncludeFields = true;
        });
```

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

无需配置。

### 23.5.6 允许尾随逗号

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.AllowTrailingCommas = true;
        });
```

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

无需配置。

### 23.5.7 允许注释

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.ReadCommentHandling = JsonCommentHandling.Skip;
        });
```

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

无需配置。

### 23.5.8 处理乱码问题

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
        });
```

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

无需配置。

### 23.5.9 不区分大小写

- `System.Text.Json` 方式

```cs
services.AddControllersWithViews()
        .AddJsonOptions(options =>
        {
            options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
        });
```

需引用 `System.Text.Json` 命名空间。

- `Newtonsoft.Json` 方式

:::tip 更多序列化配置

这里只列举常用见的序列化配置，如需查看更多配置，可查阅 [System.Text.Json 文档](https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-configure-options?pivots=dotnet-5-0)

:::

## 23.6 `DataTable`、`DataSet`、`Tuple` 元组等序列化问题

由于默认 `Furion` 采用 `System.Text.Json` 进行序列化，但是不支持复杂类型，如 `DataTable` 、 `DataSet`、`Tuple` 元组，所以需要更换成 `NewtonsoftJson` 即可，见 [JSON 序列化 - 23.5.1 自定义序列化提供器](./json-serialization#2351-自定义序列化提供器)

## 23.7 `System.Text.Json` 和 `Newtonsoft.Json` 完整差异化对比

[https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0](https://docs.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-5-0)

## 23.8 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
